home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-01 / ohlutil.zip / TAC.C < prev    next >
C/C++ Source or Header  |  1990-06-22  |  15KB  |  558 lines

  1. /* tac - concatenate and print files in reverse
  2.    Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 1, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Written by Jay Lepreau (lepreau@cs.utah.edu).
  19.    GNU enhancements by David MacKenzie (djm@ai.mit.edu). */
  20.  
  21. /* Usage: tac [-br] [-s separator] [+before] [+regex] [+separator separator]
  22.           [file...]
  23.  
  24.    Copy each FILE, or the standard input if none are given or when a
  25.    FILE name of "-" is encountered, to the standard output with the
  26.    order of the records reversed.  The records are separated by
  27.    instances of a string, or a newline if none is given.  By default, the
  28.    separator string is attached to the end of the record that it
  29.    follows in the file.
  30.  
  31.    Options:
  32.    -b, +before            The separator is attached to the beginning
  33.                 of the record that it precedes in the file.
  34.    -r, +regex            The separator is a regular expression.
  35.    -s, +separator separator    Use SEPARATOR as the record separator.
  36.  
  37.    To reverse a file byte by byte, use (in bash, ksh, or sh):
  38. tac -r -s '.\|
  39. ' file */
  40.  
  41. #include <stdio.h>
  42. #include <signal.h>
  43. #include <sys/types.h>
  44. #include "system.h"
  45. #include "getopt.h"
  46. #include "regex.h"
  47.  
  48. #ifdef STDC_HEADERS
  49. #include <stdlib.h>
  50. #include <errno.h>
  51. #else
  52. char *malloc ();
  53. char *realloc ();
  54.  
  55. extern int errno;
  56. #endif
  57.  
  58. /* The number of bytes per atomic read. */
  59. #define INITIAL_READSIZE 8192
  60.  
  61. /* The number of bytes per atomic write. */
  62. #define WRITESIZE 8192
  63.  
  64. char *mktemp ();
  65.  
  66. #ifndef _POSIX_SOURCE
  67. off_t lseek ();
  68. #endif
  69.  
  70. int cleanup ();
  71. int tac ();
  72. int tac_file ();
  73. int tac_stdin ();
  74. char *xmalloc ();
  75. char *xrealloc ();
  76. void output ();
  77. void error ();
  78. void save_stdin ();
  79. void xwrite ();
  80.  
  81. /* The name this program was run with. */
  82. char *program_name;
  83.  
  84. /* The string that separates the records of the file. */
  85. char *separator;
  86.  
  87. /* If nonzero, print `separator' along with the record preceding it
  88.    in the file; otherwise with the record following it. */
  89. int separator_ends_record;
  90.  
  91. /* 0 if `separator' is to be matched as a regular expression;
  92.    otherwise, the length of `separator', used as a sentinel to
  93.    stop the search. */
  94. int sentinel_length;
  95.  
  96. /* The length of a match with `separator'.  If `sentinel_length' is 0,
  97.    `match_length' is computed every time a match succeeds;
  98.    otherwise, it is simply the length of `separator'. */
  99. int match_length;
  100.  
  101. /* The input buffer. */
  102. char *buffer;
  103.  
  104. /* The number of bytes to read at once into `buffer'. */
  105. unsigned read_size;
  106.  
  107. /* The size of `buffer'.  This is read_size * 2 + sentinel_length + 2.
  108.    The extra 2 bytes allow `past_end' to have a value beyond the
  109.    end of `buffer' and `match_start' to run off the front of `buffer'. */
  110. unsigned buffer_size;
  111.  
  112. /* The compiled regular expression representing `separator'. */
  113. static struct re_pattern_buffer compiled_separator;
  114.  
  115. struct option longopts[] =
  116. {
  117.   {"before", 0, &separator_ends_record, 0},
  118.   {"regex", 0, &sentinel_length, 0},
  119.   {"separator", 1, NULL, 's'},
  120.   {NULL, 0, NULL, 0}
  121. };
  122.  
  123. int
  124. main (argc, argv)
  125.      int argc;
  126.      char **argv;
  127. {
  128.   char *error_message;        /* The return value from re_compile. */
  129.   int optc, longind, errors;
  130.  
  131.   program_name = argv[0];
  132.   errors = 0;
  133.   separator = "\n";
  134.   sentinel_length = 1;
  135.   separator_ends_record = 1;
  136.  
  137.   while ((optc = getopt_long (argc, argv, "brs:", longopts, &longind))
  138.      != EOF)
  139.     {
  140.       if (optc == 0 && longopts[longind].flag == NULL)
  141.     optc = longopts[longind].val;
  142.       switch (optc)
  143.     {
  144.     case 0:
  145.       break;
  146.     case 'b':
  147.       separator_ends_record = 0;
  148.       break;
  149.     case 'r':
  150.       sentinel_length = 0;
  151.       break;
  152.     case 's':
  153.       separator = optarg;
  154.       if (*separator == 0)
  155.         error (1, 0, "separator cannot be empty");
  156.       break;
  157.     default:
  158.       fprintf (stderr, "\
  159. Usage: %s [-br] [-s separator] [+before] [+regex] [+separator separator]\n\
  160.        [file...]\n",
  161.            program_name);
  162.       exit (1);
  163.     }
  164.     }
  165.  
  166.   if (sentinel_length == 0)
  167.     {
  168.       compiled_separator.allocated = 100;
  169.       compiled_separator.buffer = xmalloc (compiled_separator.allocated);
  170.       error_message = re_compile_pattern (separator, strlen (separator),
  171.                       &compiled_separator);
  172.       if (error_message)
  173.     error (1, 0, "%s", error_message);
  174.       compiled_separator.fastmap = xmalloc (256);
  175.       compiled_separator.translate = 0;
  176.     }
  177.   else
  178.     match_length = sentinel_length = strlen (separator);
  179.  
  180.   read_size = INITIAL_READSIZE;
  181.   /* A precaution that will probably never be needed. */
  182.   while (sentinel_length * 2 >= read_size)
  183.     read_size *= 2;
  184.   buffer_size = read_size * 2 + sentinel_length + 2;
  185.   buffer = xmalloc (buffer_size);
  186.   if (sentinel_length)
  187.     {
  188.       strcpy (buffer, separator);
  189.       buffer += sentinel_length;
  190.     }
  191.   else
  192.     ++buffer;
  193.  
  194.   if (optind == argc)
  195.     errors = tac_stdin ();
  196.   else
  197.     for (; optind < argc; ++optind)
  198.       {
  199.     if (strcmp (argv[optind], "-") == 0)
  200.       errors |= tac_stdin ();
  201.     else
  202.       errors |= tac_file (argv[optind]);
  203.       }
  204.  
  205.   /* Flush the output buffer. */
  206.   output ((char *) NULL, (char *) NULL);
  207.   exit (errors);
  208. }
  209.  
  210. /* The name of a temporary file containing a copy of pipe input. */
  211. char *tempfile;
  212.  
  213. /* Print the standard input in reverse, saving it to temporary
  214.    file `tempfile' first if it is a pipe.
  215.    Return 0 if ok, 1 if an error occurs. */
  216.  
  217. int
  218. tac_stdin ()
  219. {
  220.   /* Previous values of signal handlers. */
  221.   SIGTYPE (*sigint) (), (*sighup) (), (*sigterm) ();
  222.   int errors;
  223.  
  224.   /* No tempfile is needed for "tac < file". */
  225.   if (lseek (0, (off_t) 0, 0) == 0)
  226.     return tac (0, "standard input");
  227.  
  228.   sigint = signal (SIGINT, SIG_IGN);
  229.   if (sigint != SIG_IGN)
  230.     signal (SIGINT, cleanup);
  231.   sighup = signal (SIGHUP, SIG_IGN);
  232.   if (sighup != SIG_IGN)
  233.     signal (SIGHUP, cleanup);
  234.   sigterm = signal (SIGTERM, SIG_IGN);
  235.   if (sigterm != SIG_IGN)
  236.     signal (SIGTERM, cleanup);
  237.   save_stdin ();
  238.  
  239.   errors = tac_file (tempfile);
  240.  
  241.   unlink (tempfile);
  242.   signal (SIGINT, sigint);
  243.   signal (SIGHUP, sighup);
  244.   signal (SIGTERM, sigterm);
  245.   return errors;
  246. }
  247.  
  248. char template[] = "/tmp/tacXXXXXX";
  249. char workplate[sizeof template];
  250.  
  251. /* Make a copy of the standard input in `tempfile'. */
  252.  
  253. void
  254. save_stdin ()
  255. {
  256.   int fd;
  257.   int bytes_read;
  258.  
  259.   strcpy (workplate, template);
  260.   tempfile = mktemp (workplate);
  261.   fd = creat (tempfile, 0600);
  262.   if (fd == -1)
  263.     {
  264.       error (0, errno, "%s", tempfile);
  265.       cleanup ();
  266.     }
  267.   while ((bytes_read = read (0, buffer, read_size)) > 0)
  268.     if (write (fd, buffer, bytes_read) != bytes_read)
  269.       {
  270.     error (0, errno, "%s", tempfile);
  271.     cleanup ();
  272.       }
  273.   close (fd);
  274.   if (bytes_read == -1)
  275.     {
  276.       error (0, errno, "read error");
  277.       cleanup ();
  278.     }
  279. }
  280.  
  281. /* Print FILE in reverse.
  282.    Return 0 if ok, 1 if an error occurs. */
  283.  
  284. int
  285. tac_file (file)
  286.      char *file;
  287. {
  288.   int fd, errors;
  289.  
  290.   fd = open (file, 0);
  291.   if (fd == -1)
  292.     {
  293.       error (0, errno, "%s", file);
  294.       return 1;
  295.     }
  296.   errors = tac (fd, file);
  297.   close (fd);
  298.   return errors;
  299. }
  300.  
  301. /* Print in reverse the file open on descriptor FD for reading FILE.
  302.    Return 0 if ok, 1 if an error occurs. */
  303.  
  304. int
  305. tac (fd, file)
  306.      int fd;
  307.      char *file;
  308. {
  309.   /* Pointer to the location in `buffer' where the search for
  310.      the next separator will begin. */
  311.   char *match_start;
  312.   /* Pointer to one past the rightmost character in `buffer' that
  313.      has not been printed yet. */
  314.   char *past_end;
  315.   unsigned saved_record_size;    /* Length of the record growing in `buffer'. */
  316.   off_t file_pos;        /* Offset in the file of the next read. */
  317.   /* Nonzero if `output' has not been called yet for any file.
  318.      Only used when the separator is attached to the preceding record. */
  319.   int first_time = 1;
  320.   char first_char = *separator;    /* Speed optimization, non-regexp. */
  321.   char *separator1 = separator + 1; /* Speed optimization, non-regexp. */
  322.   int match_length1 = match_length - 1; /* Speed optimization, non-regexp. */
  323.   struct re_registers regs;
  324.   int errors = 0;
  325.  
  326.   /* Find the size of the input file. */
  327.   file_pos = lseek (fd, (off_t) 0, 2);
  328.   if (file_pos < 1)
  329.     return 0;            /* It's an empty file. */
  330.  
  331.   /* Arrange for the first read to lop off enough to leave the rest of the
  332.      file a multiple of `read_size'.  Since `read_size' can change, this may
  333.      not always hold during the program run, but since it usually will, leave
  334.      it here for i/o efficiency (page/sector boundaries and all that).
  335.      Note: the efficiency gain has not been verified. */
  336.   saved_record_size = file_pos % read_size;
  337.   if (saved_record_size == 0)
  338.     saved_record_size = read_size;
  339.   file_pos -= saved_record_size;
  340.   /* `file_pos' now points to the start of the last (probably partial) block
  341.      in the input file. */
  342.  
  343.   lseek (fd, file_pos, 0);
  344.   if (read (fd, buffer, saved_record_size) != saved_record_size)
  345.     {
  346.       error (0, 1, "%s", file);
  347.       return 1;
  348.     }
  349.  
  350.   match_start = past_end = buffer + saved_record_size;
  351.   /* For non-regexp search, move past impossible positions for a match. */
  352.   if (sentinel_length)
  353.     match_start -= match_length1;
  354.  
  355.   for (;;)
  356.     {
  357.       /* Search backward from `match_start' - 1 to `buffer' for a match
  358.      with `separator'; for speed, use strncmp if `separator' contains no
  359.      metacharacters.
  360.      If the match succeeds, set `match_start' to point to the start of
  361.      the match and `match_length' to the length of the match.
  362.      Otherwise, make `match_start' < `buffer'. */
  363.       if (sentinel_length == 0)
  364.     {
  365.       int i = match_start - buffer;
  366.       int ret;
  367.  
  368.       ret = re_search (&compiled_separator, buffer, i, i - 1, -i, ®s);
  369.       if (ret == -1)
  370.         match_start = buffer - 1;
  371.       else if (ret == -2)
  372.         {
  373.           cleanup ();
  374.           error (1, 0, "error in regular expression search");
  375.         }
  376.       else
  377.         {
  378.           match_start = buffer + regs.start[0];
  379.           match_length = regs.end[0] - regs.start[0];
  380.         }
  381.     }
  382.       else
  383.     {
  384.       /* `match_length' is constant for non-regexp boundaries. */
  385.       while (*--match_start != first_char
  386.          || (match_length1 && strncmp (match_start + 1, separator1,
  387.                            match_length1)))
  388.         /* Do nothing. */ ;
  389.     }
  390.  
  391.       /* Check whether we backed off the front of `buffer' without finding
  392.          a match for `separator'. */
  393.       if (match_start < buffer)
  394.     {
  395.       if (file_pos == 0)
  396.         {
  397.           /* Hit the beginning of the file; print the remaining record. */
  398.           output (buffer, past_end);
  399.           return errors;
  400.         }
  401.  
  402.       saved_record_size = past_end - buffer;
  403.       if (saved_record_size > read_size)
  404.         {
  405.           /* `buffer_size' is about twice `read_size', so since
  406.          we want to read in another `read_size' bytes before
  407.          the data already in `buffer', we need to increase
  408.          `buffer_size'. */
  409.           char *newbuffer;
  410.           int offset = sentinel_length ? sentinel_length : 1;
  411.  
  412.           read_size *= 2;
  413.           buffer_size = read_size * 2 + sentinel_length + 2;
  414.           newbuffer = xrealloc (buffer - offset, buffer_size) + offset;
  415.           /* Adjust the pointers for the new buffer location.  */
  416.           match_start += newbuffer - buffer;
  417.           past_end += newbuffer - buffer;
  418.           buffer = newbuffer;
  419.         }
  420.  
  421.       /* Back up to the start of the next bufferfull of the file.  */
  422.       if (file_pos >= read_size)
  423.         file_pos -= read_size;
  424.       else
  425.         {
  426.           read_size = file_pos;
  427.           file_pos = 0;
  428.         }
  429.       lseek (fd, file_pos, 0);
  430.  
  431.       /* Shift the pending record data right to make room for the new. */
  432.       bcopy (buffer, buffer + read_size, saved_record_size);
  433.       past_end = buffer + read_size + saved_record_size;
  434.       /* For non-regexp searches, avoid unneccessary scanning. */
  435.       if (sentinel_length)
  436.         match_start = buffer + read_size;
  437.       else
  438.         match_start = past_end;
  439.  
  440.       if (read (fd, buffer, read_size) != read_size)
  441.         {
  442.           error (0, errno, "%s", file);
  443.           return 1;
  444.         }
  445.     }
  446.       else
  447.     {
  448.       /* Found a match of `separator'. */
  449.       if (separator_ends_record)
  450.         {
  451.           char *match_end = match_start + match_length;
  452.  
  453.           /* If this match of `separator' isn't at the end of the
  454.              file, print the record. */
  455.           if (first_time == 0 || match_end != past_end)
  456.         output (match_end, past_end);
  457.           past_end = match_end;
  458.           first_time = 0;
  459.         }
  460.       else
  461.         {
  462.           output (match_start, past_end);
  463.           past_end = match_start;
  464.         }
  465.       match_start -= match_length - 1;
  466.     }
  467.     }
  468. }
  469.  
  470. /* Print the characters from START to PAST_END - 1.
  471.    If START is NULL, just flush the buffer. */
  472.  
  473. void
  474. output (start, past_end)
  475.      char *start;
  476.      char *past_end;
  477. {
  478.   static char buffer[WRITESIZE];
  479.   static int bytes_in_buffer = 0;
  480.   int bytes_to_add = past_end - start;
  481.   int bytes_available = WRITESIZE - bytes_in_buffer;
  482.  
  483.   if (start == 0)
  484.     {
  485.       xwrite (1, buffer, bytes_in_buffer);
  486.       bytes_in_buffer = 0;
  487.       return;
  488.     }
  489.   
  490.   /* Write out as many full buffers as possible. */
  491.   while (bytes_to_add >= bytes_available)
  492.     {
  493.       bcopy (start, buffer + bytes_in_buffer, bytes_available);
  494.       bytes_to_add -= bytes_available;
  495.       start += bytes_available;
  496.       xwrite (1, buffer, WRITESIZE);
  497.       bytes_in_buffer = 0;
  498.       bytes_available = WRITESIZE;
  499.     }
  500.  
  501.   bcopy (start, buffer + bytes_in_buffer, bytes_to_add);
  502.   bytes_in_buffer += bytes_to_add;
  503. }
  504.  
  505. int
  506. cleanup ()
  507. {
  508.   unlink (tempfile);
  509.   exit (1);
  510. }
  511.  
  512. void
  513. xwrite (desc, buffer, size)
  514.      int desc;
  515.      char *buffer;
  516.      int size;
  517. {
  518.   if (write (desc, buffer, size) != size)
  519.     {
  520.       error (0, errno, "write error");
  521.       cleanup ();
  522.       exit (1);
  523.     }
  524. }
  525.  
  526. /* Allocate N bytes of memory dynamically, with error checking.  */
  527.  
  528. char *
  529. xmalloc (n)
  530.      unsigned n;
  531. {
  532.   char *p;
  533.  
  534.   p = malloc (n);
  535.   if (p == 0)
  536.     {
  537.       cleanup ();
  538.       error (1, 0, "virtual memory exhausted");
  539.     }
  540.   return p;
  541. }
  542.  
  543. /* Change the size of memory area P to N bytes, with error checking. */
  544.  
  545. char *
  546. xrealloc (p, n)
  547.      char *p;
  548.      unsigned n;
  549. {
  550.   p = realloc (p, n);
  551.   if (p == 0)
  552.     {
  553.       cleanup ();
  554.       error (1, 0, "virtual memory exhausted");
  555.     }
  556.   return p;
  557. }
  558.